﻿using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata.Ecma335;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace EcoSystem.EntityFrameworkCore
{

    /// <summary>
    /// Object对象值密存储转转换器
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class EncryptionConverter<T> : ValueConverter<T, string>
    {

        public const int DefaultKeysize = 256;
        public static string DefaultPassPhrase { get; set; }
        public static byte[] DefaultInitVectorBytes { get; set; }

        public static byte[] DefaultSalt { get; set; }
        public EncryptionConverter()
            : base(x => Encrypt(x), x => Decrypt(x))
        {
            DefaultPassPhrase = "neWwAY041HLL4IM8";
            DefaultInitVectorBytes = Encoding.ASCII.GetBytes("srE49230Tf093x68");
            DefaultSalt = Encoding.ASCII.GetBytes("bgt!16kl");
        }

        private static string ObjectToString(object input)
        {
            if (null == input) { return ""; }
            var type = input.GetType();
            if (input.GetType() == typeof(string))
            {
                return (string)(object)input;
            }
            else
            {
                return type.IsPrimitive ? input.ToString() : JsonConvert.SerializeObject(input);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private static T StringToObject(string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                return default;
            }
            var type = typeof(T);
            if (type == typeof(string))
            {
                return (T)(object)input;
            }
            else
            {
                return type.IsPrimitive ? (T)Convert.ChangeType(input, type) : JsonConvert.DeserializeObject<T>(input);
            }
        }

        private static string Encrypt(T input)
        {
            try
            {
                if (input == null) { return ""; }
                string text = ObjectToString(input);
                byte[] inputData = Encoding.UTF8.GetBytes(text);
                using (var password = new Rfc2898DeriveBytes(DefaultPassPhrase, DefaultSalt))
                {
                    var keyBytes = password.GetBytes(DefaultKeysize / 8);
                    using (var symmetricKey = Aes.Create())
                    {
                        symmetricKey.Mode = CipherMode.CBC;
                        using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, DefaultInitVectorBytes))
                        {
                            using (var memoryStream = new MemoryStream())
                            {
                                using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                                {
                                    cryptoStream.Write(inputData, 0, inputData.Length);
                                    cryptoStream.FlushFinalBlock();
                                    var cipherTextBytes = memoryStream.ToArray();
                                    var rawString = Convert.ToBase64String(cipherTextBytes);
                                    return rawString;

                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                return input.ToString();
            }

        }

        private static T Decrypt(string input)
        {
            try
            {
                if (string.IsNullOrEmpty(input))
                {
                    return default;
                }
                var cipherTextBytes = Convert.FromBase64String(input);
                using (var password = new Rfc2898DeriveBytes(DefaultPassPhrase, DefaultSalt))
                {
                    var keyBytes = password.GetBytes(DefaultKeysize / 8);
                    using (var symmetricKey = Aes.Create())
                    {
                        symmetricKey.Mode = CipherMode.CBC;
                        using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, DefaultInitVectorBytes))
                        {
                            using (var memoryStream = new MemoryStream(cipherTextBytes))
                            {
                                using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                                {
                                    var plainTextBytes = new byte[cipherTextBytes.Length];
                                    int totalDecryptedByteCount = 0;
                                    while (totalDecryptedByteCount < plainTextBytes.Length)
                                    {
                                        var decryptedByteCount = cryptoStream.Read(
                                            plainTextBytes,
                                            totalDecryptedByteCount,
                                            plainTextBytes.Length - totalDecryptedByteCount
                                        );

                                        if (decryptedByteCount == 0)
                                        {
                                            break;
                                        }

                                        totalDecryptedByteCount += decryptedByteCount;
                                    }
                                    string text = Encoding.UTF8.GetString(plainTextBytes, 0, totalDecryptedByteCount);
                                    return StringToObject(text);
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                return default(T);
            }

        }
    }
}
